package fr.n7.sma.view;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JSpinner;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import fr.n7.sma.core.Main;
import fr.n7.sma.util.property.PropertyChangedEvent;
import fr.n7.sma.util.property.PropertyListener;
import fr.n7.sma.util.property.PropertyManager;
import fr.n7.sma.view.Grid.Properties;

public class PrincipalFrame extends JFrame implements PropertyListener<Properties>{

	private static final long serialVersionUID = 8848599068699675538L;

	private PropertyManager<Properties> propertyManager;

	private JSpinner agentsNbSpinner;

	private JSpinner resourcesNbSpinner;

	private JCheckBox realTimeOn;

	private JCheckBox agentsOn;
	private JCheckBox agentsImagesOn;

	private JCheckBox memoryOn;
	private JSpinner tracesLengthSpinner;
	private JLabel tracesLengthLabel;

	private JCheckBox resourcesOn;
	private JCheckBox resourcesImagesOn;

	private JCheckBox drawPheros;
	private JCheckBox putPheros;
	private JCheckBox useAttractors;
	private JButton removeAttractors;
	private JButton removePheros;
	private JButton removeAll;
	private JButton addAttractor;
	private JSpinner attractorRadius;
	private JSpinner attractorForce;
	
	private JCheckBox linesOn;
	
	private JSlider speedSlider;
	

	private Main main;
	
	private ResourceCharts resourceCharts;

	private Map<String, JCheckBox> addableResourceCheckBoxes;
	
	private Dimension labelDimension = new Dimension(200, 20);

	private JSpinner evapSpinner;

	private JCheckBox evapOn;

	private JSpinner agentsDeplSpinner;

	private JSpinner pheroForceSpinner;

	private JSpinner pheroRadiusSpinner;

	
	@SuppressWarnings("unchecked")
	public PrincipalFrame(int w, int h){
		super("SMA");
		
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		JPanel options = new JPanel();
		
		propertyManager = new PropertyManager<Properties>();
		resourceCharts = new ResourceCharts();

		//Default Values
		propertyManager.set(Properties.REAL_TIME, Boolean.class, true);
		propertyManager.set(Properties.DRAW_AGENTS, Boolean.class, true);
		propertyManager.set(Properties.DRAW_IMAGES_FOR_AGENTS, Boolean.class, true);
		propertyManager.set(Properties.DRAW_IMAGES_FOR_RESOURCES_PREFERENCE, Boolean.class, true);
		propertyManager.set(Properties.DRAW_LINES_PREFERENCE, Boolean.class, true);
		propertyManager.set(Properties.DRAW_MEMORY, Boolean.class, false);
		propertyManager.set(Properties.DRAW_ATTRACTOR, Boolean.class, true);
		propertyManager.set(Properties.USE_ATTRACTOR, Boolean.class, false);
		propertyManager.set(Properties.PHERO_FORCE, Double.class, 5d);
		propertyManager.set(Properties.PHERO_RADIUS, Integer.class, 2);
		propertyManager.set(Properties.EVAP_ON, Boolean.class, false);
		propertyManager.set(Properties.PUT_PHERO, Boolean.class, false);
		propertyManager.set(Properties.EVAPORATION_RATE_PERCENT, Integer.class, 20);
		propertyManager.set(Properties.DRAW_RESOURCES, Boolean.class, true);

		propertyManager.set(Properties.VISION, Integer.class, 2);
		propertyManager.set(Properties.AGENT_NUMBER, Integer.class, 30);
		propertyManager.set(Properties.RESOURCE_NUMBER, Integer.class, (int)(Math.min(650, w*h/3.846)));
		propertyManager.set(Properties.MEMORY_LENGTH, Integer.class, 20);

		String[] s = {"computer", "harddisk", "battery"};
		ArrayList<String> l = new ArrayList<String>();
		for(String name : s) l.add(name);
		propertyManager.set(Properties.ADDABLE_RESOURCES, List.class, new ArrayList<String>(l));
		propertyManager.set(Properties.RECOGNISED_RESOURCES, List.class, new ArrayList<String>(l));
		propertyManager.set(Properties.RESOURCES, List.class, new ArrayList<String>(l));

		propertyManager.set(Properties.CHARTS_REFRESH_RATE, Integer.class, (int)(10000000/(w*h)));
		propertyManager.set(Properties.GRID_REFRESH_RATE, Integer.class, (int)(5000000/(w*h)));
		propertyManager.set(Properties.PAUSE, Integer.class, (int)(w*h)/30);

		options.setLayout(new BoxLayout(options, BoxLayout.Y_AXIS));

		options.add(buildModelSettings(w, h));
		options.add(buildGraphicalSettings(w, h));
		options.add(Box.createGlue());
		
		addListeners();
		
		JTabbedPane tabs = new JTabbedPane();
		JSplitPane jsp = new JSplitPane(JSplitPane.VERTICAL_SPLIT, new JScrollPane(options), new Help());
		jsp.setDividerLocation(0.7);
		jsp.setContinuousLayout(true);
		jsp.setOneTouchExpandable(true);
		tabs.add(jsp, "Options");

		Component help = Help.newHelp(false, 
				"Ci-dessous, dès que la première ressource a été découverte, s'affichent :<br/>"+
				"<ul>" +
				"<li> <FONT color=\"#0000DD\"> Les histogrammes en bleu </FONT> présentent" +
				"		<b>nb(ndepl)*ndepl</b> en fonction de <b>ndepl</b> où :" +
				"	<ul>" +
				"	<li> <b>nb(ndepl)</b> est le nombre de fois que <b>ndepl</b> " +
				"			déplacements ont été nécessaires" +
				"	 		pour rencontrer consécutivement deux mêmes ressources.</li>" +
				"	<li> <b>ndepl</b> est le nombre de déplacements qui ont été nécessaires" +
				"	 		pour rencontrer consécutivement deux mêmes ressources.</li>" +
				"	</ul>"+
				"<li> <FONT color=\"#DD0000\">Les courbes en rouge </FONT> présentent " +
				"		<b> moyenne(nb(nbdepl), nbdepl)</b> en fonction de <b>nbtot</b></li>" +
				"	<ul>" +
				"	<li> <b>nb(ndepl)</b> est le nombre de fois que <b>ndepl</b> " +
				"			déplacements ont été nécessaires" +
				"	 		pour rencontrer consécutivement deux mêmes ressources.</li>" +
				"	<li> <b>ndepl</b> est le nombre de déplacements qui ont été nécessaires" +
				"	 		pour rencontrer consécutivement deux mêmes ressources.</li>" +
				"	<li> <b>nbtot</b> est le nombre total de déplacements effectués.</li>" +
				"	</ul>"+
				"</ul>" +
				"");
		JSplitPane resources = new JSplitPane(JSplitPane.VERTICAL_SPLIT, help, new JScrollPane(resourceCharts));
		resources.setOneTouchExpandable(true);
		resources.setResizeWeight(0);
		resources.setContinuousLayout(true);
		tabs.add(resources, "Histogrammes");
		
		JPanel right = new JPanel();
		
		JSplitPane p = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, createGrid(w, h), right);
		p.setOneTouchExpandable(true);
		p.setResizeWeight(1);
		p.setContinuousLayout(true);
		JPanel up = new JPanel();
		up.add(new Player(main));
		up.add(slidersPanel());
		up.setLayout(new BoxLayout(up, BoxLayout.Y_AXIS));
		right.setLayout(new BorderLayout());
		right.add(up, BorderLayout.NORTH);
		right.add(tabs, BorderLayout.CENTER);
		add(p);

		tabs.add(new JScrollPane(buildResourcesPanel(s)), "Ressources");
		tabs.add(new JScrollPane(new AgentsPanel(propertyManager, main.getGridModel())), "Agents");

		propertyManager.addListener(Properties.USE_ATTRACTOR, Boolean.class, this);
		propertyManager.addListener(Properties.DRAW_ATTRACTOR, Boolean.class, this);
		
	}
	
	private JPanel slidersPanel() {
		JPanel p = new JPanel();
		p.setLayout(new BorderLayout());
		int pause = propertyManager.get(Properties.PAUSE, Integer.class);
		int value = (int)Math.sqrt(50000./pause);
		speedSlider = new JSlider(1, 100, Math.max(0, Math.min(100, value)));
		updateRefreshRates(value);
		speedSlider.addChangeListener(new SliderListener());
		p.add(new JLabel("Vitesse : "), BorderLayout.WEST);
		p.add(speedSlider, BorderLayout.CENTER);
		return p;
	}

	@SuppressWarnings("unchecked")
	private JPanel buildResourcesPanel(String[] s){
		JPanel resourcesPanel = new JPanel();
		resourcesPanel.setLayout(new BoxLayout(resourcesPanel, BoxLayout.Y_AXIS));
		resourcesPanel.add(new JLabel("Resources à ajouter lors d'une augmentation du nombre de resources :"));
		
		addableResourceCheckBoxes = new HashMap<String, JCheckBox>();
		
		List<String> cs = propertyManager.get(Properties.ADDABLE_RESOURCES, List.class);
				
		for(String name : s){
			JCheckBox jcb = new JCheckBox(name);
			jcb.setSelected(cs.contains(name));
			addableResourceCheckBoxes.put(name, jcb);
			resourcesPanel.add(jcb);
			jcb.addActionListener(new AddableResourcesListener(name, jcb));
		}
		
		return resourcesPanel;
	}
	
	private JPanel buildModelSettings(int w, int h){

		JLabel name;

		JPanel modelSettings = new JPanel();
		BoxLayout layout = new BoxLayout(modelSettings, BoxLayout.Y_AXIS);
		
		modelSettings.setLayout(layout);
		modelSettings.setBorder(BorderFactory.createTitledBorder("Options du modèle"));

		JPanel agentsNb = new JPanel();
		agentsNb.setLayout(new BoxLayout(agentsNb, BoxLayout.X_AXIS));
		modelSettings.add(agentsNb);
		name = new JLabel("Nombre d'agents : ");
		name.setPreferredSize(new Dimension(labelDimension));
		SpinnerNumberModel agentNumberModel = new  SpinnerNumberModel(0+propertyManager.get(Properties.AGENT_NUMBER, Integer.class), (int)0, w*h, (int)1);
		agentsNbSpinner = new JSpinner(agentNumberModel);
		agentsNbSpinner.setMaximumSize(agentsNbSpinner.getMinimumSize());
		name.setLabelFor(agentsNbSpinner);
		agentsNb.add(name);
		agentsNb.add(agentsNbSpinner);
		agentsNb.setAlignmentX(0);
		
		JPanel resourcesNb = new JPanel();
		resourcesNb.setLayout(new BoxLayout(resourcesNb, BoxLayout.X_AXIS));
		modelSettings.add(resourcesNb);
		name = new JLabel("Nombre de ressources : ");
		name.setPreferredSize(new Dimension(labelDimension));
		SpinnerNumberModel resourceNumberModel = new  SpinnerNumberModel(0+propertyManager.get(Properties.RESOURCE_NUMBER, Integer.class), (int)0, w*h, (int)10);
		resourcesNbSpinner = new JSpinner(resourceNumberModel);
		resourcesNbSpinner.setMaximumSize(resourcesNbSpinner.getMinimumSize());
		name.setLabelFor(resourcesNbSpinner);
		resourcesNb.add(name);
		resourcesNb.add(resourcesNbSpinner);
		resourcesNb.setAlignmentX(0);

		JPanel agentsDeplPanel = new JPanel();
		agentsDeplPanel.setLayout(new BoxLayout(agentsDeplPanel, BoxLayout.X_AXIS));
		name = new JLabel("Liberté de déplacement de l'agent : ");
		name.setPreferredSize(new Dimension(labelDimension));
		SpinnerNumberModel agentDeplModel = new  SpinnerNumberModel(0+propertyManager.get(Properties.VISION, Integer.class), 0, 4, 1);
		agentsDeplSpinner = new JSpinner(agentDeplModel);
		agentsDeplSpinner.setMaximumSize(agentsDeplSpinner.getMinimumSize());
		name.setLabelFor(agentsDeplSpinner);
		agentsDeplPanel.add(name);
		agentsDeplPanel.add(agentsDeplSpinner);
		agentsDeplPanel.setAlignmentX(0);
		modelSettings.add(agentsDeplPanel);
		
		
		modelSettings.setAlignmentX(0);

		//Attractor
		
		useAttractors		= new JCheckBox("Les agents tiennent compte des attracteurs");
		useAttractors.setSelected(propertyManager.get(Properties.USE_ATTRACTOR, Boolean.class));
		useAttractors.setAlignmentX(0);
		putPheros 			= new JCheckBox("Les agents déposent des attracteurs");
		putPheros.setSelected(propertyManager.get(Properties.PUT_PHERO, Boolean.class));
		putPheros.setAlignmentX(0);
		evapOn 			= new JCheckBox("Les attracteurs déposés s'avaporent");
		evapOn.setSelected(propertyManager.get(Properties.EVAP_ON, Boolean.class));
		evapOn.setAlignmentX(0);
		modelSettings.add(useAttractors);
		modelSettings.add(putPheros);
		modelSettings.add(evapOn);

		//Evaporation
		JPanel evapPanel 	= new JPanel();
		evapPanel.setLayout(new BoxLayout(evapPanel, BoxLayout.X_AXIS));
		evapPanel.add(new JLabel("Taux d'évaporation : "));
		SpinnerNumberModel evapModel = new  SpinnerNumberModel(0+propertyManager.get(Properties.EVAPORATION_RATE_PERCENT, Integer.class), 0, 100, 1);
		evapSpinner = new JSpinner(evapModel);
		evapSpinner.setPreferredSize(evapSpinner.getMinimumSize());
		evapSpinner.setMaximumSize(evapSpinner.getMinimumSize());
		evapPanel.add(evapSpinner);
		evapPanel.add(new JLabel("%"));
		evapPanel.setAlignmentX(0);
		modelSettings.add(evapPanel);
		
		//Phero
		JPanel pheroForcePanel 	= new JPanel();
		pheroForcePanel.setLayout(new BoxLayout(pheroForcePanel, BoxLayout.X_AXIS));
		pheroForcePanel.add(new JLabel("Force des attracteurs déposés : "));
		SpinnerNumberModel pheroForceModel = new  SpinnerNumberModel(0d+propertyManager.get(Properties.PHERO_FORCE, Double.class), 0d, 500d, 1d);
		pheroForceSpinner = new JSpinner(pheroForceModel);
		pheroForceSpinner.setPreferredSize(pheroForceSpinner.getMinimumSize());
		pheroForceSpinner.setMaximumSize(pheroForceSpinner.getMinimumSize());
		pheroForcePanel.add(pheroForceSpinner);
		pheroForcePanel.setAlignmentX(0);
		modelSettings.add(pheroForcePanel);
		
		JPanel pheroRadiusPanel 	= new JPanel();
		pheroRadiusPanel.setLayout(new BoxLayout(pheroRadiusPanel, BoxLayout.X_AXIS));
		pheroRadiusPanel.add(new JLabel("Rayon des attracteurs déposés : "));
		SpinnerNumberModel pheroRadiusModel = new  SpinnerNumberModel(0+propertyManager.get(Properties.PHERO_RADIUS, Integer.class), 0, Math.min(w, h), 1);
		pheroRadiusSpinner = new JSpinner(pheroRadiusModel);
		pheroRadiusSpinner.setPreferredSize(pheroRadiusSpinner.getMinimumSize());
		pheroRadiusSpinner.setMaximumSize(pheroRadiusSpinner.getMinimumSize());
		pheroRadiusPanel.add(pheroRadiusSpinner);
		pheroRadiusPanel.setAlignmentX(0);
		modelSettings.add(pheroRadiusPanel);

		JPanel attractorPanel = new JPanel();
		attractorPanel.setLayout(new BoxLayout(attractorPanel, BoxLayout.X_AXIS));
		addAttractor = new JButton("Ajouter un attracteur");
		
		SpinnerNumberModel radiusModel = new  SpinnerNumberModel(10, 1, Math.max(w, h)*4, 1);
		attractorRadius = new JSpinner(radiusModel);
		attractorRadius.setPreferredSize(attractorRadius.getMinimumSize());
		attractorRadius.setMaximumSize(attractorRadius.getMinimumSize());
		JPanel radius = new JPanel();
		radius.setLayout(new BoxLayout(radius, BoxLayout.X_AXIS));
		radius.add(new JLabel("rayon : "));
		radius.add(attractorRadius);
		
		SpinnerNumberModel forceModel = new  SpinnerNumberModel(10, 1, 1000, 1);
		attractorForce = new JSpinner(forceModel);
		attractorForce.setPreferredSize(attractorForce.getMinimumSize());
		attractorForce.setMaximumSize(attractorForce.getMinimumSize());
		JPanel force = new JPanel();
		force.setLayout(new BoxLayout(force, BoxLayout.X_AXIS));
		force.add(new JLabel("force : "));
		force.add(attractorForce);
		
		attractorPanel.add(addAttractor);
		attractorPanel.add(radius);
		attractorPanel.add(force);
		attractorPanel.setAlignmentX(0);
		modelSettings.add(attractorPanel);

		removeAttractors = new JButton("Enlever les attracteurs ajoutés");
		modelSettings.add(removeAttractors);
		
		removePheros = new JButton("Enlever les attracteurs déposés par les agents");
		modelSettings.add(removePheros);
		
		removeAll = new JButton("Enlever tous les attracteurs");
		modelSettings.add(removeAll);

		return modelSettings;
	}

	private JPanel buildGraphicalSettings(int w, int h){

		JPanel graphicalSettings = new JPanel();
		graphicalSettings.setLayout(new BoxLayout(graphicalSettings, BoxLayout.Y_AXIS));
		graphicalSettings.setBorder(BorderFactory.createTitledBorder("Options d'affichage"));
		
		//Real Time
		realTimeOn 			= new JCheckBox("Affichage Temps-réel");
		graphicalSettings.add(realTimeOn);
		
		//Agents
		JPanel agents 		= new JPanel();
		agents.setLayout(new BoxLayout(agents, BoxLayout.X_AXIS));
		agentsOn 			= new JCheckBox("Afficher les agents");
		agentsImagesOn 		= new JCheckBox(" et utiliser des images");
		
		agents.add(agentsOn);
		agents.add(agentsImagesOn);
		agents.setAlignmentX(0);
		graphicalSettings.add(agents);
		
		//Resources
		JPanel resources 	= new JPanel();
		resources.setLayout(new BoxLayout(resources, BoxLayout.X_AXIS));
		resourcesOn 		= new JCheckBox("Afficher les resources");
		resourcesImagesOn 	= new JCheckBox("et utiliser des images (si possible)");
		
		resources.add(resourcesOn);
		resources.add(resourcesImagesOn);
		resources.setAlignmentX(0);
		graphicalSettings.add(resources);

		//Pheros
		JPanel pheroPanel 	= new JPanel();
		pheroPanel.setLayout(new BoxLayout(pheroPanel, BoxLayout.X_AXIS));
		drawPheros 			= new JCheckBox("Afficher les attracteurs");
		pheroPanel.add(drawPheros);
		pheroPanel.setAlignmentX(0);
		graphicalSettings.add(pheroPanel);

		//Memory
		JPanel memoryPanel 	= new JPanel();
		memoryPanel.setLayout(new BoxLayout(memoryPanel, BoxLayout.X_AXIS));
		memoryOn 			= new JCheckBox("Afficher les traces");
		SpinnerNumberModel traceLengthModel = new  SpinnerNumberModel(0+propertyManager.get(Properties.MEMORY_LENGTH, Integer.class), (int)0, (int)Integer.MAX_VALUE, (int)1);
		tracesLengthSpinner = new JSpinner(traceLengthModel);
		tracesLengthSpinner.setPreferredSize(tracesLengthSpinner.getMinimumSize());
		tracesLengthSpinner.setMaximumSize(tracesLengthSpinner.getMinimumSize());

		memoryPanel.add(memoryOn);
		tracesLengthLabel = new JLabel(" de longueur : ");
		tracesLengthLabel.setLabelFor(tracesLengthSpinner);
		memoryPanel.add(tracesLengthLabel);
		memoryPanel.add(tracesLengthSpinner);
		memoryPanel.setAlignmentX(0);
		graphicalSettings.add(memoryPanel);
		
		//Lines
		linesOn 			= new JCheckBox("Afficher la grille (si possible)");
		
		linesOn.setAlignmentX(0);
		graphicalSettings.add(linesOn);
		
		realTimeOn		.setSelected(propertyManager.get(Properties.REAL_TIME, Boolean.class));
		agentsOn		.setSelected(propertyManager.get(Properties.DRAW_AGENTS, Boolean.class));
		agentsImagesOn	.setSelected(propertyManager.get(Properties.DRAW_IMAGES_FOR_AGENTS, Boolean.class));
		memoryOn		.setSelected(propertyManager.get(Properties.DRAW_MEMORY, Boolean.class));
		drawPheros		.setSelected(propertyManager.get(Properties.DRAW_ATTRACTOR, Boolean.class));
		linesOn			.setSelected(propertyManager.get(Properties.DRAW_LINES_PREFERENCE, Boolean.class));
		resourcesOn		.setSelected(propertyManager.get(Properties.DRAW_RESOURCES, Boolean.class));
		resourcesImagesOn.setSelected(propertyManager.get(Properties.DRAW_IMAGES_FOR_RESOURCES_PREFERENCE, Boolean.class));

		graphicalSettings.setAlignmentX(0);
		
		return graphicalSettings;
	}

	private void addListeners(){
		realTimeOn.addActionListener(new BooleanListener(
				Properties.REAL_TIME,
				realTimeOn));
		agentsOn.addActionListener(new BooleanListener(
				Properties.DRAW_AGENTS,
				agentsOn));
		agentsImagesOn.addActionListener(new BooleanListener(
				Properties.DRAW_IMAGES_FOR_AGENTS,
				agentsImagesOn));
		memoryOn.addActionListener(new BooleanListener(
				Properties.DRAW_MEMORY,
				memoryOn));
		drawPheros.addActionListener(new BooleanListener(
				Properties.DRAW_ATTRACTOR,
				drawPheros));
		putPheros.addActionListener(new BooleanListener(
				Properties.PUT_PHERO,
				putPheros));
		useAttractors.addActionListener(new BooleanListener(
				Properties.USE_ATTRACTOR,
				useAttractors));
		evapOn.addActionListener(new BooleanListener(
				Properties.EVAP_ON,
				evapOn));
		resourcesImagesOn.addActionListener(new BooleanListener(
				Properties.DRAW_IMAGES_FOR_RESOURCES_PREFERENCE,
				resourcesImagesOn));
		resourcesOn.addActionListener(new BooleanListener(
				Properties.DRAW_RESOURCES,
				resourcesOn));
		linesOn.addActionListener(new BooleanListener(
				Properties.DRAW_LINES_PREFERENCE,
				linesOn));
		SpinnerListener<Integer> memoryLengthListener = new SpinnerListener<Integer>(
				Properties.MEMORY_LENGTH,
				tracesLengthSpinner,
				Integer.class);
		tracesLengthSpinner.addChangeListener(memoryLengthListener);
		SpinnerListener<Integer> agentNumberListener = new SpinnerListener<Integer>(
				Properties.AGENT_NUMBER,
				agentsNbSpinner,
				Integer.class);
		agentsNbSpinner.addChangeListener(agentNumberListener);
		SpinnerListener<Integer> resourceNumberListener = new SpinnerListener<Integer>(
				Properties.RESOURCE_NUMBER,
				resourcesNbSpinner,
				Integer.class);
		resourcesNbSpinner.addChangeListener(resourceNumberListener);
		SpinnerListener<Integer> agentDeplListener = new SpinnerListener<Integer>(
				Properties.VISION,
				agentsDeplSpinner,
				Integer.class);
		agentsDeplSpinner.addChangeListener(agentDeplListener);
		SpinnerListener<Integer> pheroRadiusListener = new SpinnerListener<Integer>(
				Properties.PHERO_RADIUS,
				pheroRadiusSpinner,
				Integer.class);
		pheroRadiusSpinner.addChangeListener(pheroRadiusListener);
		SpinnerListener<Double> pheroForceListener = new SpinnerListener<Double>(
				Properties.PHERO_FORCE,
				pheroForceSpinner,
				Double.class);
		pheroForceSpinner.addChangeListener(pheroForceListener);
		
		addAttractor.addActionListener(new AddAttractorButtonListener());
		removeAttractors.addActionListener(new RemoveAttractorButtonListener(true, false));
		removePheros.addActionListener(new RemoveAttractorButtonListener(false, true));
		removeAll.addActionListener(new RemoveAttractorButtonListener(true, true));
		SpinnerListener<Integer> evapSpinnerListener = new SpinnerListener<Integer>(
				Properties.EVAPORATION_RATE_PERCENT,
				evapSpinner,
				Integer.class);
		evapSpinner.addChangeListener(evapSpinnerListener);
	}

	private class BooleanListener implements ActionListener{

		private Properties p;
		private JCheckBox jCB;

		public BooleanListener(Properties p, JCheckBox jCB){
			this.p = p;
			this.jCB = jCB;
		}

		
		public void actionPerformed(ActionEvent ae) {
			boolean value = jCB.isSelected();
			propertyManager.set(p, Boolean.class, value);
		}
	}

	private void updateRefreshRates(int value){
		propertyManager.set(Properties.GRID_REFRESH_RATE, Integer.class, value*value*5);
		propertyManager.set(Properties.CHARTS_REFRESH_RATE, Integer.class, value*value*20);
		propertyManager.set(Properties.PAUSE, Integer.class, 50000/(value*value));
	}
	
	private class SliderListener implements ChangeListener{

		
		public void stateChanged(ChangeEvent e) {
			updateRefreshRates(speedSlider.getValue());
		}
	}
	
	private class AddableResourcesListener implements ActionListener{

		private JCheckBox jCB;
		private String name;
		

		public AddableResourcesListener(String name, JCheckBox jCB){
			this.name = name;
			this.jCB = jCB;
		}

		@SuppressWarnings("unchecked")
		
		public void actionPerformed(ActionEvent ae) {
			List<String> cs = propertyManager.get(Properties.ADDABLE_RESOURCES, List.class);
			if(jCB.isSelected()){
				cs.add(name);
			}else{
				cs.remove(name);
			}
			propertyManager.propertyChanged(Properties.ADDABLE_RESOURCES, List.class);
		}
	}

	private class RemoveAttractorButtonListener implements ActionListener {

		private boolean attractor;
		private boolean phero;
		
		public RemoveAttractorButtonListener(boolean attractor, boolean phero) {
			this.attractor = attractor;
			this.phero = phero;
		}
		
		@Override
		public void actionPerformed(ActionEvent arg0) {
			main.getGrid().clearAttractors(attractor, phero);
		}

	}
	
	private class AddAttractorButtonListener implements ActionListener {

		@Override
		public void actionPerformed(ActionEvent arg0) {
			setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
			main.getGrid().addAttractor((Integer)attractorRadius.getValue(), (Integer)attractorForce.getValue(), PrincipalFrame.this);
		}

	}
	
	private class SpinnerListener<C> implements ChangeListener {

		private Properties p;
		private JSpinner jS;
		private Class<C> c; 

		public SpinnerListener(Properties p, JSpinner jS, Class<C> c){
			this.p = p;
			this.jS = jS;
			this.c = c;
		}

		
		public void stateChanged(ChangeEvent e) {
			propertyManager.set(p, c, c.cast(jS.getValue()));
		}

	}

	private JPanel createGrid(int w, int h){
		boolean tracesOn = PrincipalFrame.this.drawPheros.isSelected();
		boolean agentsOn = PrincipalFrame.this.agentsOn.isSelected();
		boolean resourcesOn = PrincipalFrame.this.resourcesOn.isSelected();
		boolean memoryOn = PrincipalFrame.this.memoryOn.isSelected();
		boolean drawImagesForAgents = PrincipalFrame.this.agentsImagesOn.isSelected();
		boolean drawImagesForResourcesPreference = PrincipalFrame.this.resourcesImagesOn.isSelected();
		boolean drawLinesPreference = PrincipalFrame.this.linesOn.isSelected();
		Dimension frameDim = new Dimension(300, 300);

		Map<String, BufferedImage> resourceSymbols = new HashMap<String, BufferedImage>();
		Map<String, Color> resourceColors = new HashMap<String, Color>();
		try {
			resourceSymbols.put("battery", ImageIO.read(new File("resources/battery.png")));
			resourceColors.put("battery", Color.red);
			resourceSymbols.put("computer", ImageIO.read(new File("resources/computer.png")));
			resourceColors.put("computer", Color.blue);
			resourceSymbols.put("harddisk", ImageIO.read(new File("resources/drive-harddisk.png")));
			resourceColors.put("harddisk", Color.black);
		} catch (IOException e1) {
			e1.printStackTrace();
		}

		propertyManager.set(Properties.DRAW_ATTRACTOR, Boolean.class, tracesOn);
		propertyManager.set(Properties.DRAW_AGENTS, Boolean.class, agentsOn);
		propertyManager.set(Properties.DRAW_RESOURCES, Boolean.class, resourcesOn);
		propertyManager.set(Properties.DRAW_MEMORY, Boolean.class, memoryOn);
		propertyManager.set(Properties.DRAW_IMAGES_FOR_AGENTS, Boolean.class, drawImagesForAgents);
		propertyManager.set(Properties.DRAW_IMAGES_FOR_RESOURCES_PREFERENCE, Boolean.class, drawImagesForResourcesPreference);
		propertyManager.set(Properties.DRAW_LINES_PREFERENCE, Boolean.class, drawLinesPreference);

		main = new Main(
				w, h,
				resourceSymbols, resourceColors,
				propertyManager,
				frameDim,
				resourceCharts);
		
		return main;
	}

	@Override
	public void propertyChanged(PropertyChangedEvent<Properties> e) {
		switch(e.getKey()){
		case USE_ATTRACTOR :
			useAttractors.setSelected((Boolean)e.getValue());
			break;
		case DRAW_ATTRACTOR :
			drawPheros.setSelected((Boolean)e.getValue());
			break;
		}
	}

}
